#include <iostream>
#include <cstdlib>
#include "midi.h"
using namespace std;

Midi::Midi() 
     : m_pSeq(NULL), m_outPortId(-1), m_inPortId(-1), m_queueId(-1), 
       m_pollFdCount(0), m_pPollFd(NULL), m_abort(false)
{
     createPorts();
     createQueue();
     initPollDescriptors();
}

Midi::~Midi() {
     if (m_pPollFd)
	  delete[] m_pPollFd;
}

void Midi::createPorts(void) {
     if (snd_seq_open(&m_pSeq, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0) {
	  cerr << "Error opening ALSA sequencer." << endl;
	  exit(1);
     }
     snd_seq_set_client_name(m_pSeq, "7bw");
     m_outPortId = snd_seq_create_simple_port(m_pSeq, "out-1",
					   SND_SEQ_PORT_CAP_READ |
					   SND_SEQ_PORT_CAP_SUBS_READ,
					   SND_SEQ_PORT_TYPE_APPLICATION);
     if (m_outPortId < 0) {
	  cerr << "Error creating midi out port." << endl;
	  exit(1);
     }
     m_inPortId = snd_seq_create_simple_port(m_pSeq, "in-1",
					     SND_SEQ_PORT_CAP_WRITE |
					     SND_SEQ_PORT_CAP_SUBS_WRITE,
					     SND_SEQ_PORT_TYPE_APPLICATION);
     if (m_inPortId < 0) {
	  cerr << "Error creating midi out port." << endl;
	  exit(1);
     }
}

void Midi::createQueue() {
     m_queueId = snd_seq_alloc_queue(m_pSeq);
     snd_seq_start_queue(m_pSeq, m_queueId, NULL);
     snd_seq_drain_output(m_pSeq);
}

void Midi::sendEvent(snd_seq_event_t *ev) {
     snd_seq_ev_set_subs(ev); /* send to all subscribers */
     snd_seq_ev_set_direct(ev); /* send unbuffered */
     snd_seq_ev_set_source(ev, m_outPortId); /* set source port */
     snd_seq_event_output_direct(m_pSeq, ev); /* send it! */
}

void Midi::setTempo(int bpm) {
       snd_seq_queue_tempo_t *queue_tempo = NULL;
       int tempo = (int)(6e7 / ((double)bpm * (double)Midi::ppq) * (double)Midi::ppq);
       int ret = snd_seq_queue_tempo_malloc(&queue_tempo);
       if (!queue_tempo || ret) {
	    cerr << "midi: seq queue tempo malloc failed" << endl;
	    exit(1);
       }
       snd_seq_queue_tempo_set_tempo(queue_tempo, tempo);
       snd_seq_queue_tempo_set_ppq(queue_tempo, Midi::ppq);
       snd_seq_set_queue_tempo(m_pSeq, m_queueId, queue_tempo);
       snd_seq_queue_tempo_free(queue_tempo); 
}

void Midi::scheduleNoteOff(int chan, int oldNote, int tick) {
     if (oldNote < 0 || oldNote > 127)
	  return;
     snd_seq_event_t ev;
     snd_seq_ev_clear(&ev);
     snd_seq_ev_set_noteoff(&ev, chan, oldNote, 0);
     snd_seq_ev_schedule_tick(&ev, m_queueId,  1, tick);
     snd_seq_ev_set_source(&ev, m_outPortId);
     snd_seq_ev_set_subs(&ev);
     snd_seq_event_output_direct(m_pSeq, &ev);
}

void Midi::scheduleNoteOn(int chan, int note, int vel, int tick) {
     assert (note >= 0 && note < 128);
     assert (vel >= 0 && vel < 128);
     assert (chan >= 0 && chan < 16);
     snd_seq_event_t ev;
     snd_seq_ev_clear(&ev);
     snd_seq_ev_set_noteon(&ev, chan, note, vel);
     snd_seq_ev_schedule_tick(&ev, m_queueId,  1, tick);
     snd_seq_ev_set_source(&ev, m_outPortId);
     snd_seq_ev_set_subs(&ev);
     snd_seq_event_output_direct(m_pSeq, &ev);
}

void Midi::scheduleControlChange(int chan, int num, int val, int tick) {
     assert (num >= 0 && num < 128);
     assert (val >= 0 && val < 128);
     assert (chan >= 0 && chan < 16);
     snd_seq_event_t ev;
     snd_seq_ev_clear(&ev);
     snd_seq_ev_set_controller(&ev, chan, num, val);
     snd_seq_ev_schedule_tick(&ev, m_queueId,  1, tick);
     snd_seq_ev_set_source(&ev, m_outPortId);
     snd_seq_ev_set_subs(&ev);
     snd_seq_event_output_direct(m_pSeq, &ev);
}

void Midi::scheduleEcho(int tick, int data) {
     snd_seq_event_t ev;
     snd_seq_ev_clear(&ev);
     ev.type = SND_SEQ_EVENT_ECHO;
     ev.data.raw8.d[0] = data;
     snd_seq_ev_schedule_tick(&ev, m_queueId,  1, tick);
     snd_seq_ev_set_dest(&ev, snd_seq_client_id(m_pSeq), m_inPortId);
     snd_seq_event_output_direct(m_pSeq, &ev);
}

void Midi::abort() {
     m_abort = true;
}

void Midi::initPollDescriptors() {
     if (!m_pPollFd) {
	  m_pollFdCount = snd_seq_poll_descriptors_count(m_pSeq, POLLIN);
	  m_pPollFd = new struct pollfd[m_pollFdCount];
     }
}

int Midi::checkInputEvents() {
     int ret = Midi::None;
     if (poll(m_pPollFd, m_pollFdCount, 100000) > 0) {
	  for (int i = 0; i < m_pollFdCount; i++) {
	       if (m_pPollFd[i].revents > 0) 
		    ret = handleInputEvents();
	  }
     }  
     return ret;
}

// returns true if it gets an echo event
int Midi::handleInputEvents() {
     snd_seq_event_t *ev;
     do {
	  snd_seq_event_input(m_pSeq, &ev);
	  switch (ev->type) {
	  case SND_SEQ_EVENT_ECHO:
	       if (ev->data.raw8.d[0] == Midi::Loop)
		    return Midi::Loop;
	       if (ev->data.raw8.d[0] == Midi::IncRow)
		    return Midi::IncRow;
	       break;
	  case SND_SEQ_EVENT_NOTEON:
	       break;        
	  case SND_SEQ_EVENT_CONTROLLER:
	       break;
	  case SND_SEQ_EVENT_PITCHBEND:
	       break;
	       }
	  snd_seq_free_event(ev);
     } while (snd_seq_event_input_pending(m_pSeq, 0) > 0);
     return Midi::None;
}

// TODO - figure out actual channels to send on
void Midi::allNotesOff() {
     scheduleControlChange(1, 120, 0, 0);
}

void Midi::clearQueue(int afterTick) {

     snd_seq_remove_events_t *remove_ev;

     snd_seq_remove_events_malloc(&remove_ev);
     snd_seq_remove_events_set_queue(remove_ev, m_queueId);
     snd_seq_timestamp_t time;
     time.tick = afterTick;
     snd_seq_remove_events_set_time(remove_ev, &time);
     // only remove output events, don't remove note offs
     snd_seq_remove_events_set_condition(remove_ev, SND_SEQ_REMOVE_OUTPUT |
					 SND_SEQ_REMOVE_IGNORE_OFF|
					 SND_SEQ_REMOVE_TIME_AFTER);
     snd_seq_remove_events(m_pSeq, remove_ev);
     snd_seq_remove_events_free(remove_ev);
}

void Midi::playNote(int num, int vel, int len, int chan) {
     snd_seq_event_t ev;
     snd_seq_ev_clear(&ev);
     snd_seq_ev_set_note(&ev, chan, num, vel, len);
     // schedule in past - play now
     snd_seq_ev_schedule_tick(&ev, m_queueId,  1, 0);
     snd_seq_ev_set_source(&ev, m_outPortId);
     snd_seq_ev_set_subs(&ev);
     snd_seq_event_output_direct(m_pSeq, &ev);
}
